//
//  GCDTableViewController.m
//  GCDDemo
//
//  Created by 苏沫离 on 2018/8/1.
//  Copyright © 2018年 苏沫离. All rights reserved.
//
#define CellIdentifer @"UITableViewCell"

#import "GCDTableViewController.h"
#import "QueueTaskViewController.h"
#import "GCDGroupTableController.h"
#import "GCDQueueTableViewController.h"
#import "GCDBarrierTableViewController.h"
#import "GCDBlockTableViewController.h"

@interface GCDTableViewController ()
@property (nonatomic ,strong) NSArray<NSString *> *titleArray;
@end

@implementation GCDTableViewController

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CellIdentifer];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(threadExitNotice:) name:NSThreadWillExitNotification object:nil];
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)threadExitNotice:(NSNotification *)notification
{
    NSLog(@" threadExitNotice ------------ %@",notification.object);
}

#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.titleArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifer forIndexPath:indexPath];
    cell.textLabel.text = self.titleArray[indexPath.row];
    cell.textLabel.numberOfLines = 0;
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    if (indexPath.row == 0)
    {
        GCDBlockTableViewController *blockVC = [[GCDBlockTableViewController alloc] init];
        [self.navigationController pushViewController:blockVC animated:YES];
    }
    else if (indexPath.row == 1)
    {
        GCDQueueTableViewController *gcdVC = [[GCDQueueTableViewController alloc] init];
        [self.navigationController pushViewController:gcdVC animated:YES];
    }
    else if (indexPath.row == 2)
    {
        QueueTaskViewController *gcdVC = [[QueueTaskViewController alloc] init];
        [self.navigationController pushViewController:gcdVC animated:YES];
    }
    else if (indexPath.row == 3)
    {
        GCDBarrierTableViewController *gcdVC = [[GCDBarrierTableViewController alloc] init];
        [self.navigationController pushViewController:gcdVC animated:YES];
    }
    else if (indexPath.row == 4)
    {
        GCDGroupTableController *groupVC = [[GCDGroupTableController alloc] init];
        [self.navigationController pushViewController:groupVC animated:YES];
    }
    else if (indexPath.row == 5)
    {
        [self semaphoreTestMethod];//信号量
    }
    else if (indexPath.row == 6)
    {
       [self holdThreadCountMethod];//控制线程数量
    }
    else if (indexPath.row == 7)
    {
        [self dispatch_afterMethod];//延迟执行
    }
    else if (indexPath.row == 8)
    {
        [self dispatch_applyMethod];//快速迭代
    }
    else if (indexPath.row == 9)
    {
        [self newSerialQueueDeadLockMethod];//串行队列+异步+同步 造成的死锁
    }
    else if (indexPath.row == 10)
    {
        
    }

}

#pragma mark - 延迟执行

- (void)dispatch_afterMethod
{
    NSLog(@"开始处理 ====== %@",NSThread.currentThread);
    
    dispatch_queue_t queue = dispatch_queue_create("dispatch_after", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        
        NSLog(@"开始执行");
        // DISPATCH_TIME_NOW 当前时间开始
        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC));
        dispatch_after(time, queue, ^{
            NSLog(@"延迟十秒钟 === %@",NSThread.currentThread);
        });
        
        NSLog(@"结束执行");
    });
    
    NSLog(@"中间处理 ------ %@",NSThread.currentThread);
    
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"延迟十秒钟 ---- %@",NSThread.currentThread);
    });
    
    
    NSLog(@"结束处理 ====== %@",NSThread.currentThread);
}


- (void)dispatch_applyMethod
{
    NSLog(@"开始处理");
    dispatch_queue_t queue = dispatch_queue_create("dispatch_apply", DISPATCH_QUEUE_SERIAL);
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"开始执行：%zd---%@",index, [NSThread currentThread]);
        [NSThread sleepForTimeInterval:(arc4random() % 6 + 1)];//模拟耗时任务
        NSLog(@"结束执行：%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"结束处理");
}

#pragma mark - 信号量
//GCD 不像 NSOperation 那样有直接提供线程数量控制方法，但是通过 GCD 的 semaphore 功能一样可以达到控制线程数量的效果。

- (void)semaphoreTestMethod
{
    //并发线程
    dispatch_queue_t queue = dispatch_queue_create("com.demo.maxCONCURRENT", DISPATCH_QUEUE_CONCURRENT);
    //创建一个semaphore,并设置最大信号量，最大信号量表示并发数
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    dispatch_apply(6, queue, ^(size_t i) {
        long single = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        // 只有当信号量大于 0 的时候，线程将信号量减 1，程序向下执行
        // 否则线程会阻塞并且一直等待，直到信号量大于 0
        dispatch_async(queue, ^{
            NSLog(@"开始执行第 %zu 次任务 == single : %ld----- %@",i,single,NSThread.currentThread);
            [NSThread sleepForTimeInterval:(i % 2 == 0 ? 2 : 3)];//模拟耗时任务
            long value = dispatch_semaphore_signal(semaphore);// 当线程任务执行完成之后，发送一个信号，增加信号量。
            NSLog(@"结束执行第 %zu 次任务 == single : %ld----- %@",i,value,NSThread.currentThread);
        });
    });
}

#pragma mark - 控制线程数量

/*
 利用 GCD 的 dispatch_group_t 和 semaphore 功能，我们可以做到控制线程数量，并且在所有任务执行完成之后得到通知。
 */
- (void)holdThreadCountMethod
{
    dispatch_queue_t queue = dispatch_queue_create("com.objective_c_language.gcd.ThreadCount", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    for (int i = 0 ; i < 10 ; i ++)
    {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        
        // 只有当信号量大于 0 的时候，线程将信号量减 1，程序向下执行
        // 否则线程会阻塞并且一直等待，直到信号量大于 0
        
        dispatch_group_async(group, queue, ^{
            NSLog(@"执行第 %d 次任务 == %@",i,NSThread.currentThread);
            [NSThread sleepForTimeInterval:10];//处理耗时任务
            dispatch_semaphore_signal(semaphore);// 当线程任务执行完成之后，发送一个信号，增加信号量。
        });
    }
    
    //既控制了线程数量，也在执行任务完成之后得到了通知。
    dispatch_group_notify(group, queue, ^{
        NSLog(@"任务结束 ------ %@",NSThread.currentThread);
    });
}

#pragma mark - 串行队列死锁

- (void)newSerialQueueDeadLockMethod
{
    //串行队列+异步+同步 造成死锁
    dispatch_queue_t queue = dispatch_queue_create("newQueueDeadLockMethod", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        
        NSLog(@"异步任务A --- %@",NSThread.currentThread);
        
        //程序报错： Thread 4: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
        dispatch_sync(queue, ^{
            NSLog(@"同步任务B --- %@",NSThread.currentThread);
        });
    });
    
    NSLog(@"异步执行之后的代码");
    
    
    /*
     主线程主队列中执行任务 initDispatchQueue,进入了这个方法，在这个方法里面创建了一个串行队列
     给这个串行队列添加了一个异步任务，由于是异步任务，所以会开启一条新的线程，为了方便描述，我们把新开的这个线程记做线程A, 把这个任务记做任务A，也由于是异步任务，主线程就不会等待这个任务返回，就接着往下执行其他任务了。
     接下来的分析就到了这个线程A上，这个任务A被添加到串行队列之后就开始在线程A上执行，打印出了我们的第一条信息，也证明了不是在主线程,这个也没问题。
     线程A开始执行这个任务A，进入这个任务A之后在这个任务A里面又同步在串行队列里面添加任务，记做任务B，由于任务B是dispatch_sync函数同步添加的，需要立马被执行，就等待线程A执行它
     但是这个任务B是添加到串行队列的末尾的，线程A在没有执行完当前任务A是不会去执行它的，这样就造成线程A在等待当前任务A执行完，任务B又在等待线程A执行它，就形成了死锁
     */
}



#pragma mark - setter and getter

- (NSArray<NSString *> *)titleArray
{
    if (_titleArray == nil)
    {
        _titleArray = @[@"dispatch_block",
                        @"dispatch_queue",
                        @"queue_task",
                        @"dispatch_barrier",
                        @"dispatch_group",
                        @"semaphore",
                        @"控制线程数量",
                        @"dispatch_after",
                        @"dispatch_apply",
                        @"newQueue DeadLock"];
    }
    return _titleArray;
}

@end
